package proxy;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;

import message.Request;
import message.RequestWrapper;
import message.Response;
import message.ResponseWrapper;
import message.request.DownloadFileRequest;
import message.request.InfoRequest;
import message.request.ListRequest;
import message.request.UploadRequest;
import message.request.VersionRequest;
import message.response.CorruptedMessageResponse;
import message.response.MessageResponse;
import server.IFileServer;
import util.Config;
import util.IntegrityTool;

public class FileserverWorker implements IFileServer {

	private InetAddress fsHost;
	private int fsTcpPort;
	
	int maxRetryCount;

	private Socket socket;
	private ObjectOutputStream out;
	private ObjectInputStream in;
	
	//private Utils.EncObjectOutputStream out;
	//private Utils.EncObjectInputStream in;

	private Config messages;

	private IntegrityTool integrityTool;

	public FileserverWorker(InetAddress fsHost, int fsTcpPort) {
		this.fsHost = fsHost;
		this.fsTcpPort = fsTcpPort;
		messages = new Config("messages");
		//how many retries if request or response are corrupted
		maxRetryCount = 1;

		try {
			integrityTool = new IntegrityTool(new Config("proxy"));
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}

	private Response sendRequest(Request request) throws IOException {

		Response response = null;
		ResponseWrapper responseW = null;
		RequestWrapper requestW = null;
		boolean responseIntegrityVerified = false;
		boolean requestIntegrityVerified = false;
		
		do {
			String hmac = "";
			try {
				hmac = integrityTool.createMac(request.toString());
			} catch (Exception e) {
				e.printStackTrace();
			}

			requestW = new RequestWrapper(request, hmac);
			
			connect();

			try {
				if (socket == null || socket.isClosed())
					connect();

				out.writeObject(requestW);
				responseW = (ResponseWrapper) in.readObject();

			} catch (SocketException e) {
				e.printStackTrace();
				return new MessageResponse(
						messages.getString("error.noConnection"));
			} catch (EOFException e) {
				e.printStackTrace();
				return new MessageResponse(
						messages.getString("error.noConnection"));
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
				return new MessageResponse(
						messages.getString("error.unexpected"));
			} finally {
				disconnect();
			}

			response = responseW.getResponse();

			//Check if Request was corrupted
			if(response instanceof CorruptedMessageResponse)
				requestIntegrityVerified = false;
			else 
				requestIntegrityVerified = true;
			
			try {
				responseIntegrityVerified = integrityTool.verifyMac(
						response.toString(), responseW.getHmac());
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			maxRetryCount--;
			
			//retry as long as request or response are corrupted and maxRetryCount is non negative 
		} while (!(responseIntegrityVerified && requestIntegrityVerified) && maxRetryCount >= 0);

		// TODO do something when response is still corrupted
		if (responseIntegrityVerified && requestIntegrityVerified)
			return response;
		else if(responseIntegrityVerified)
			return new MessageResponse("Request corrupted! "
					+ "Received Message: " + requestW.toString());
		else
			return new MessageResponse("Response corrupted! "
					+ "Received Message: " + requestW.toString());

	}

	public void connect() throws IOException {
		try {

			socket = new Socket(this.fsHost, this.fsTcpPort);

			out = new ObjectOutputStream(this.socket.getOutputStream());
			in = new ObjectInputStream(this.socket.getInputStream());
			
		//	out = new Utils.EncObjectOutputStream(this.socket.getOutputStream());
		//	in = new Utils.EncObjectInputStream(this.socket.getInputStream());

		} catch (UnknownHostException e) {
			e.printStackTrace();
			System.out.println(new MessageResponse(messages
					.getString("error.unexpected")));
		}
	}

	public void disconnect() throws IOException {
		if (socket != null && !socket.isClosed()) {
			socket.shutdownOutput();
			socket.shutdownInput();
			socket.close();
		}
		try {

			if (socket != null && !socket.isClosed())
				socket.close();

			socket = null;
			in = null;
			out = null;

		} catch (IOException e) {
			e.printStackTrace();
			System.out.println(new MessageResponse(messages
					.getString("error.unexpected")));
		}
	}

	@Override
	public Response list() throws IOException {
		return sendRequest(new ListRequest());
	}

	@Override
	public Response download(DownloadFileRequest request) throws IOException {
		return sendRequest(request);
	}

	@Override
	public Response info(InfoRequest request) throws IOException {
		return sendRequest(request);
	}

	@Override
	public Response version(VersionRequest request) throws IOException {
		return sendRequest(request);
	}

	@Override
	public MessageResponse upload(UploadRequest request) throws IOException {

		Response response = sendRequest(request);

		if (response instanceof CorruptedMessageResponse)
			return new MessageResponse(response.toString());
		else
			return (MessageResponse) response;
	}

}
